/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package coalescent.br;

import coalescent.br.BRSK69.Tracker;
import coalescent.data.K69Data;
import coalescent.data.MutableK69Data;
import coalescent.model.K69;
import coalescent.model.KC64.EventType;

import commons.core.Statistic;

import java.math.BigDecimal;

/**
 * Recursion for a configuration (statistic) based on the infinite-sites-data. The concept of
 * merge-allele was introduced which is needed for computing probabilities. When mutation happens,
 * the mutated allele may become one (the merge-allele) of the alleles in the sample.
 *
 * @param <A>
 * @param <O> mutation type
 * @param <D>
 * @param <S>
 * @param <R>
 * @author Susanta Tewari
 */
public abstract class BRSK69<A, O, D extends K69Data<A, O, D>, S extends Statistic<D, S>,
                             R extends BRSK69<A, O, D, S, R>>
        extends BRSKC64<Tracker<A, O, D, S, R>, A, K69, D, S, R> {

    /**
     * Constructs ...
     *
     *
     * @param statistic
     */
    public BRSK69(S statistic) {
        super(statistic);
    }

    /**
     * Method description
     *
     *
     * @param allele
     *
     * @return
     */
    @Override
    protected final Tracker<A, O, D, S, R> applyMutation(A allele) {

        D data                              = getStatistic().getData();
        MutableK69Data<A, O, D> mutableData = data.getMutable();
        O mutation                          = data.getImmediateMutations(allele).iterator().next();
        A mergeAllele                       = null;


        // increase frequency of the merge allele
        if (data.isMergeMutation(allele, mutation)) {

            mergeAllele = data.mutateBackward(allele, mutation);

            if (mergeAllele == null) {
                mergeAllele = data.getWildAllele();
            }

            int old_freq = data.getAlleleFrequency(mergeAllele);

            mutableData.setAlleleFrequency(mergeAllele, old_freq + 1);
        }

        /**
         * note that the allele must be removed before the mutation is removed otherwise [allele]
         * would not be recognized. This is because removal of mutation changes all the alleles.
         */
        mutableData.removeAllele(allele);
        mutableData.removeMutation(mutation);

        return new Tracker<>(mergeAllele, allele, EventType.MUTATION, reflect());
    }

    /**
     * Method description
     *
     *
     * @param allele
     *
     * @return
     */
    @Override
    protected final Tracker<A, O, D, S, R> createTrackerInfoForCoalescent(A allele) {
        return new Tracker<>(null, allele, EventType.COALESCENT, reflect());
    }

    /**
     * Method description
     *
     *
     * @param type
     * @param ancestralConfig
     */
    @Override
    protected final void checkForCndAncestralProb(EventType type, R ancestralConfig) {

        super.checkForCndAncestralProb(type, ancestralConfig);


        // check allele: the allele in ancestor must be present in this recursion
        if (!getStatistic().getData().getAlleles().contains(
                ancestralConfig.getTracker().getMergeAllele())) {
            throw new IllegalArgumentException("invalid ancestral config: allele check failed");
        }
    }

    /**
     * Probability: 1/n (no-merge allele); (n(j) + 1)/n (merge allele)
     *
     * @param ancestralConfig
     *
     * @return
     */
    @Override
    protected final BigDecimal cndAncestralProbMutation(R ancestralConfig) {

        A mergeAllele  = ancestralConfig.getTracker().getMergeAllele();
        D data         = getStatistic().getData();
        BigDecimal den = new BigDecimal(data.getSampleSize());

        if (mergeAllele == null) {
            return BigDecimal.valueOf(1.0).divide(den);
        } else {

            BigDecimal num = new BigDecimal(data.getAlleleFrequency(mergeAllele) + 1);

            return num.divide(den);
        }
    }

    /**
     * Overridden implementation: {@code mutationCount + sampleSize - 1}
     *
     * @return
     */
    @Override
    public final int transitionIndex() {

        D data            = getStatistic().getData();
        int sampleSize    = data.getSampleSize();
        int mutationCount = data.getMutationCount();

        return mutationCount + sampleSize - 1;
    }

    /**
     * Class description
     *
     *
     * @param <A>
     * @param <O>
     * @param <D>
     * @param <S>
     * @param <R>
     *
     * @version        Enter version here..., 12/10/09
     * @author         Susanta Tewari
     */
    @SuppressWarnings("ProtectedInnerClass")
    protected static class Tracker<A, O, D extends K69Data<A, O, D>, S extends Statistic<D, S>,
                                   R extends BRSK69<A, O, D, S, R>>
            extends BRSPopGen.Tracker<A, EventType, K69, D, S, R> {

        /** Field description */
        private A mergeAllele;

        /**
         * Constructs ...
         *
         *
         * @param mergeAllele
         * @param allele
         * @param eventType
         * @param producer
         */
        protected Tracker(A mergeAllele, A allele, EventType eventType, R producer) {

            super(allele, eventType, producer);

            this.mergeAllele = mergeAllele;
        }

        /**
         * Method description
         *
         *
         * @return
         */
        protected final A getMergeAllele() {
            return mergeAllele;
        }
    }
}
